/*
 Xern Entity Relocation Network
 Copyright (C) 2011 Ronie Salgado <roniesalg@gmail.com>

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

#include <boost/date_time/gregorian/gregorian.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/lexical_cast.hpp>
#include "Xern/IClientModule.hpp"
#include "Xern/IResourceLoadedCallback.hpp"
#include "Xern/IRigidBody.hpp"
#include "Xern/IPhysicsEngine.hpp"
#include "Xern/DataFilter.hpp"
#include "Xern/Log.hpp"
#include "Xern/Engine.hpp"
#include "Xern/Exceptions.hpp"
#include "Xern/ResourceManager.hpp"
#include "Xern/WorldManager.hpp"
#include "Xern/RegionLoader.hpp"
#include "ClientSession.hpp"
#include "RemoteRegion.hpp"
#include "RemotePlayer.hpp"

namespace XernProtocol
{
	// EntityAddResourceCallback
	class EntityAddResourceCallback: public IResourceLoadedCallback,
		public boost::enable_shared_from_this<EntityAddResourceCallback>
	{
	public:
		EntityAddResourceCallback(boost::shared_ptr<ClientSession> client);
		~EntityAddResourceCallback();

		void OnLoadedResource(boost::shared_ptr<IResource> resource);

		uuid entityId;
		uuid resourceId;
		bool veto;
		boost::shared_ptr<IResource> resource;

	private:
		boost::shared_ptr<ClientSession> client;
	};

	EntityAddResourceCallback::EntityAddResourceCallback(boost::shared_ptr<ClientSession> client)
		: client(client)
	{
		veto = false;
	}

	EntityAddResourceCallback::~EntityAddResourceCallback()
	{
	}

	void EntityAddResourceCallback::OnLoadedResource(boost::shared_ptr<IResource> resource)
	{
		assert(resource);
		this->resource = resource;
		client->OnFinishAdd(shared_from_this());
	}

	// EntityLoadedShapeCallback
	// TODO: Clean up this.
	class EntityLoadedShapeCallback: public IResourceLoadedCallback,
		public boost::enable_shared_from_this<EntityLoadedShapeCallback>
	{
	public:
		EntityLoadedShapeCallback(boost::shared_ptr<ClientSession> client);
		~EntityLoadedShapeCallback();

		void OnLoadedResource(boost::shared_ptr<IResource> resource);
		bool IsCompleted() const;

		uuid entity;
		float mass;
		bool veto;
		IRigidBody::RigidBodyType type;
		Vector3 linearFactor;
		Vector3 angularFactor;
		float linearSleep, angularSleep;
		boost::shared_ptr<ICollisionShape> shape;

	private:
		bool completed;
		boost::shared_ptr<ClientSession> client;
	};

	EntityLoadedShapeCallback::EntityLoadedShapeCallback(boost::shared_ptr<ClientSession> client)
		: client(client)
	{
		veto = false;
		completed = false;
	}

	EntityLoadedShapeCallback::~EntityLoadedShapeCallback()
	{
	}

	void EntityLoadedShapeCallback::OnLoadedResource(boost::shared_ptr<IResource> resource)
	{
		// Cast the shape.
		assert(resource);
		shape = boost::dynamic_pointer_cast<ICollisionShape> (resource);
		if(!shape)
			LogConsole->Error("Expected collision shape.");

		// Set the completed flag.
		completed = true;

		// Notify the session.
		client->OnFinishPhysicUpdate(shared_from_this());
	}

	bool EntityLoadedShapeCallback::IsCompleted() const
	{
		return completed;
	}

	// ClientSession.
	const posix_time::ptime ClientSession::TimeEpoch(boost::gregorian::date(2000, boost::gregorian::Jan, 1), posix_time::seconds(0));
	const posix_time::time_duration ClientSession::CheckPingInterval = posix_time::milliseconds(2000);

	ClientSession::ClientSession(boost::shared_ptr<Session> session)
		: session(session), transferManager(session)
	{
		state = CS_INITIATING;

		lastPingId = 0;
		lastPingTime = TimeEpoch;
		currentPing = 0;
	}

	ClientSession::~ClientSession()
	{
	}

	void ClientSession::Opened()
	{
		Engine::Get().GetResourceManager()->RegisterResourceListener(shared_from_this());
	}

	void ClientSession::Closed()
	{
		Engine::Get().GetResourceManager()->UnregisterResourceListener(shared_from_this());
	}

	void ClientSession::OnAddedResource(boost::shared_ptr<Entity> entity, boost::shared_ptr<IResource> resource)
	{
	}

	void ClientSession::OnRemovedResource(boost::shared_ptr<Entity> entity, boost::shared_ptr<IResource> resource)
	{
	}

	void ClientSession::OnUpdateState(boost::shared_ptr<IResource> resource, boost::shared_ptr<IDataFilter> stateData)
	{
	}

	bool ClientSession::OnDelayedLoad(const uuid &id, boost::shared_ptr<IResourceLoadedCallback> callback)
	{
		transferManager.DownloadResource(id, callback);
		return true;
	}

	void ClientSession::Think(float delta)
	{
		// Receive the client messages.
		boost::shared_ptr<IMessage> message = session->Receive();
		for(; message; message = session->Receive())
		{
			// Process only xern messages.
			boost::shared_ptr<Message> xmsg = boost::dynamic_pointer_cast<Message> (message);
			if(!xmsg)
				continue;

			// Send messages into the transfer manager.
			if(transferManager.ProcessMessage(xmsg))
				continue;

			// Dispatch the message into lower levels.
			switch(xmsg->GetType())
			{
			case MT_SESSION_PING_REQUEST:
				OnSessionPingRequest(boost::static_pointer_cast<SessionPingRequest> (xmsg));
				break;
			case MT_SESSION_PING_RESPONSE:
				OnSessionPingResponse(boost::static_pointer_cast<SessionPingResponse> (xmsg));
				break;
			case MT_ENTITY_ADDED:
				OnEntityAdded(boost::static_pointer_cast<EntityAdded> (xmsg));
				break;
			case MT_ENTITY_REMOVED:
				OnEntityRemoved(boost::static_pointer_cast<EntityRemoved> (xmsg));
				break;
			case MT_ENTITY_MOVEMENT_UPDATES:
				OnEntityMovementUpdates(boost::static_pointer_cast<EntityMovementUpdates> (xmsg));
				break;
			case MT_ENTITY_PHYSIC_UPDATES:
				OnEntityPhysicUpdates(boost::static_pointer_cast<EntityPhysicUpdates> (xmsg));
				break;
			case MT_ENTITY_ADDED_RESOURCE:
				OnEntityAddedResource(boost::static_pointer_cast<EntityAddedResource> (xmsg));
				break;
			case MT_ENTITY_REMOVED_RESOURCE:
				OnEntityRemovedResource(boost::static_pointer_cast<EntityRemovedResource> (xmsg));
				break;
			case MT_PLAYER_ENTITY_RESPONSE:
				OnPlayerEntityResponse(boost::static_pointer_cast<PlayerEntityResponse> (xmsg));
				break;
			case MT_REGION_LIST_RESPONSE:
				OnRegionListResponse(boost::static_pointer_cast<RegionListResponse> (message));
				break;
			case MT_RESOURCE_STATE:
				OnResourceState(boost::static_pointer_cast<ResourceState> (xmsg));
				break;
			default:
				LogConsole->Warning("Received unknown/unexpected message[type %d]", xmsg->GetType());
				break;
			}
		}

		// Send messages according to the state.
		switch(state)
		{
		case CS_INITIATING:
			RequestRegion();
			break;
		default:
			break;
		}

		// Check the ping.
		posix_time::ptime now = GetCurrentTime();
		if(now - lastPingTime > CheckPingInterval)
			CheckPing();
	}

	void ClientSession::RequestRegion()
	{
		// Send a region list request.
		boost::shared_ptr<RegionListRequest> req(new RegionListRequest());
		session->Send(req);

		// Change the client state.
		state = CS_WAIT_REGION;
	}

	posix_time::ptime ClientSession::TransformTime(u64 sentTime) const
	{
		// Calculate the local sent time.
		posix_time::ptime serverTime = TimeEpoch + posix_time::milliseconds(sentTime);
		return serverTime + serverTimeDelta;
	}

	float ClientSession::CalculateDelta(u64 sentTime) const
	{
		// Calculate the delta time.
		posix_time::time_duration delta = GetCurrentTime() - TransformTime(sentTime);
		return delta.total_milliseconds()*0.001f;
	}

	void ClientSession::CheckPing()
	{
		// Read the current time.
		posix_time::ptime now = GetCurrentTime();
		lastPingTime = now;

		// Create the ping request.
		boost::shared_ptr<SessionPingRequest> req(new SessionPingRequest());
		req->pingId = ++lastPingId;
		req->sentTime = (now - TimeEpoch).total_milliseconds();

		// Send the request.
		session->Send(req);
	}

	void ClientSession::OnSessionPingRequest(boost::shared_ptr<SessionPingRequest> request)
	{
		// Read the current time.
		posix_time::ptime now = GetCurrentTime();

		// Create the response.
		boost::shared_ptr<SessionPingResponse> resp(new SessionPingResponse());
		resp->pingId = request->pingId;
		resp->receiveTime = (now - TimeEpoch).total_milliseconds();

		// Send it.
		session->Send(resp);
	}

	void ClientSession::OnSessionPingResponse(boost::shared_ptr<SessionPingResponse> response)
	{
		// Check for delayed responses.
		if(response->pingId != lastPingId)
			return;

		// Read the current time and calculate the ping.
		posix_time::ptime now = GetCurrentTime();
		posix_time::ptime serverTime = TimeEpoch + posix_time::milliseconds(response->receiveTime);
		posix_time::time_duration delta = (now - lastPingTime)/2;
		serverTimeDelta = (lastPingTime + delta) - serverTime;
		currentPing = delta.total_milliseconds();

		// Check next ping.
		CheckPing();
	}

	void ClientSession::OnEntityMovementUpdates(boost::shared_ptr<EntityMovementUpdates> message)
	{
		// Calculate the movement delta.
		float delta = CalculateDelta(message->time);

		// Get the world manager.
		boost::shared_ptr<WorldManager> worldMan = Engine::Get().GetWorldManager();

		// Read all of the updates
		size_t numupdates = message->updates.size();
		for(size_t i = 0; i < numupdates; i++)
		{
			// Get the update.
			const EntityMovementUpdates::Update &up = message->updates[i];

			// Find the entity.
			boost::shared_ptr<Entity> entity = worldMan->FindChildById(up.id);
			if(!entity)
				continue;

			// Reparent the entity, if necessary.
			boost::shared_ptr<Entity> parent = entity->GetParent();
			if(parent && parent->GetId() != up.parentId)
			{
				parent = worldMan->FindChildById(up.parentId);
				if(parent)
				{
					parent->AddChild(entity);
				}
				else
				{
					LogConsole->Warning("Failed to reparent entity. Unknown parent id.");
				}
			}

			// Get the old position.
			const ReversibleTransform &old = entity->GetTransform();

			// Perform lag compensation.
			Vector3 position = up.position + up.velocity*delta + up.acceleration*(delta*delta*0.5f);
			Vector3 velocity = up.velocity + up.acceleration*delta;

			// TODO: Correct angular data.

			// Calculate the transform.
			OrthoTransform from, to;
			from.SetVector(up.position);
			from.SetMatrix(QRotMatrix3(up.rotation));

			to.SetVector(position);
			to.SetMatrix(QRotMatrix3(up.rotation));

			// Calculate the position difference.
			Vector3 diff = to.GetVector() - old.GetVector();
			float distance = diff.Length();

			// Smooth out the differences.
			if(distance > 0.1f && distance < 2.0f)
			{
				from = old;
				to.SetVector(old.GetVector() + diff*0.1f);
			}

			// Store the data.
			boost::shared_ptr<IRigidBody> body = entity->GetRigidBody();
			if(body && body->GetMass() > X_EPSILON)
			{
				body->SetTransform(to);
				body->SetLinearVelocity(velocity);
				body->SetAngularVelocity(up.angularVelocity);

				// Don't move is the distance is to small.
				if(distance < X_EPSILON)
					continue;

				// Give the extrapolation task to the physics engine.
				boost::shared_ptr<Region> region = entity->GetRegion();
				if(region)
				{
					boost::shared_ptr<IPhysicRegion> physicRegion = region->GetPhysicRegion();
					if(physicRegion)
						physicRegion->MoveObject(body, from, to);
				}
			}
			else
			{
				entity->SetTransform(to);
				entity->SetLinearVelocity(velocity);
				entity->SetLinearAcceleration(up.acceleration);
				entity->SetAngularVelocity(up.angularVelocity);
			}
		}
	}

	void ClientSession::OnEntityPhysicUpdates(boost::shared_ptr<EntityPhysicUpdates> message)
	{
		// Get the world manager.
		boost::shared_ptr<WorldManager> worldMan = Engine::Get().GetWorldManager();

		// Read all of the updates
		size_t numupdates = message->updates.size();
		for(size_t i = 0; i < numupdates; i++)
		{
			// Get the update.
			const EntityPhysicUpdates::Update &up = message->updates[i];

			// Find the entity.
			boost::shared_ptr<Entity> entity = worldMan->FindChildById(up.entity);
			if(entity)
			{
				// Check for the none type.
				if(up.physicType == IRigidBody::RBT_NONE)
				{
					entity->SetRigidBody(boost::shared_ptr<IRigidBody> ());
					continue;
				}

				// Get the actual rigid body, avoid needless modifications.
				boost::shared_ptr<IRigidBody> oldBody = entity->GetRigidBody();
				if(oldBody && oldBody->GetType() == up.physicType &&
				   CompareFloat(oldBody->GetMass(), up.mass) == 0)
				{
					boost::shared_ptr<ICollisionShape> shape = oldBody->GetCollisionShape();
					if((shape && shape->GetId() == up.collisionShape) ||
					   (!shape && up.collisionShape.is_nil()))
					{
						oldBody->SetLinearFactor(up.linearFactor);
						oldBody->SetAngularFactor(up.angularFactor);
						oldBody->SetSleepingThresholds(up.linearSleepThreshold, up.angularSleepThreshold);
						continue;
					}
				}
			}

			// Create the callback.
			boost::shared_ptr<EntityLoadedShapeCallback> callback(new EntityLoadedShapeCallback(shared_from_this()));
			callback->entity = up.entity;
			callback->type = IRigidBody::RigidBodyType(up.physicType);
			callback->mass = up.mass;
			callback->linearFactor = up.linearFactor;
			callback->angularFactor = up.angularFactor;
			callback->linearSleep = up.linearSleepThreshold;
			callback->angularSleep = up.angularSleepThreshold;

			// Store the callback.
			updatePhysicsCallback[up.entity] = callback;

			// Load the collision shape.
			Engine::Get().GetResourceManager()->DelayedLoad(up.collisionShape, callback);
		}
	}

	void ClientSession::OnEntityAdded(boost::shared_ptr<EntityAdded> added)
	{
		// Get the world manager.
		boost::shared_ptr<WorldManager> worldMan = Engine::Get().GetWorldManager();

		size_t numentities = added->entityData.size();
		for(size_t i = 0; i < numentities; i++)
		{
			EntityAdded::Data &data = added->entityData[i];

			// Check if the entity was already added.
			if(worldMan->FindChildById(data.id))
				continue;

			// Create a remote object entity.
			boost::shared_ptr<RemoteObject> entity(new RemoteObject());
			entity->SetId(data.id);

			// Find the parent.
			boost::shared_ptr<Entity> parent = worldMan->FindChildById(data.parentId);
			if(parent)
				parent->AddChild(entity);

			// Find delayed resources.
			DelayedResources::iterator it = delayedResources.find(data.id);
			while(it != delayedResources.end())
			{
				// Check the entity id.
				if(it->first != data.id)
					break;

				// Link the resource with the entity.
				entity->AddResource(it->second);

				// Remove the delayed resource.
				delayedResources.erase(it++);
			}

			// Perform the delayed physic update.
			UpdatePhysicsCallbacks::iterator pit = updatePhysicsCallback.find(data.id);
			if(pit != updatePhysicsCallback.end())
			{
				boost::shared_ptr<EntityLoadedShapeCallback> callback = pit->second;
				if(callback->IsCompleted()) // Re-finish.
					OnFinishPhysicUpdate(callback);
			}
		}
	}

	void ClientSession::OnEntityRemoved(boost::shared_ptr<EntityRemoved> removed)
	{
		// Get the world manager.
		boost::shared_ptr<WorldManager> worldMan = Engine::Get().GetWorldManager();

		size_t numentities = removed->entities.size();
		for(size_t i = 0; i < numentities; i++)
		{
			uuid id = removed->entities[i];

			// Find the removed entity.
			boost::shared_ptr<Entity> ent = worldMan->FindChildById(id);
			if(!ent)
				continue;

			// Remove the entity.
			boost::shared_ptr<Entity> parent = ent->GetParent();
			if(parent)
				parent->RemoveChild(ent);
		}
	}

	void ClientSession::OnEntityAddedResource(boost::shared_ptr<EntityAddedResource> message)
	{
		// Get the resource manager.
		boost::shared_ptr<ResourceManager> resourceManager = Engine::Get().GetResourceManager();

		// Find the resource
		boost::shared_ptr<IResource> resource = resourceManager->FindResource(message->resourceId);

		// Now, try to load the resource from the cache.
		if(!resource)
		{
			try
			{
				resource = resourceManager->LoadFromCache(message->resourceHash);
			}
			catch(const XernException &e)
			{
				LogConsole->Error("Failed to load networked resource: %s", e.what());
				return;
			}

			if(resource)
			{
				// Rename the resource.
				resourceManager->RenameResource(resource, message->resourceId);
			}
		}

		// Try to add to the parent entity.
		if(resource)
		{
			// Find the parent entity.
			boost::shared_ptr<Entity> parent = Engine::Get().GetWorldManager()->FindChildById(message->entityId);
			if(parent)
			{
				// Add the resource into the parent entity.
				parent->AddResource(resource);
			}
			else
			{
				// Store the resource for delayed adding.
				delayedResources.insert(std::make_pair(message->entityId, resource));
			}
		}
		else
		{
			// Find an existing add callback.
			AddResourcesCallbacks::iterator it = addResourcesCallbacks.find(ResourceBinding(message->entityId, message->resourceId));
			if(it != addResourcesCallbacks.end())
			{
				// Unveto the callback.
				boost::shared_ptr<EntityAddResourceCallback> callback = it->second;
				callback->veto = true;
				return;
			}

			// Create the download callback.
			boost::shared_ptr<EntityAddResourceCallback> callback(new EntityAddResourceCallback(shared_from_this()));
			callback->entityId = message->entityId;
			callback->resourceId = message->resourceId;

			// Store the callback.
			addResourcesCallbacks.insert(std::make_pair(ResourceBinding(callback->entityId, callback->resourceId), callback));

			// Download the resource.
			transferManager.DownloadResource(message->resourceId, callback);
		}
	}

	void ClientSession::OnEntityRemovedResource(boost::shared_ptr<EntityRemovedResource> message)
	{
		// Get the resource manager.
		boost::shared_ptr<ResourceManager> resourceManager = Engine::Get().GetResourceManager();

		// Find the resource
		boost::shared_ptr<IResource> resource = resourceManager->FindResource(message->resourceId);

		if(resource)
		{
			// Find the parent entity.
			boost::shared_ptr<Entity> parent = Engine::Get().GetWorldManager()->FindChildById(message->entityId);
			if(parent)
			{
				// Remove the resource from the parent entity.
				parent->RemoveResource(resource);
			}
			else
			{
				// Remove the resource from delayed adding.
				DelayedResources::iterator it = delayedResources.find(message->entityId);
				while(it != delayedResources.end())
				{
					if(it->first != message->entityId)
						break;

					boost::shared_ptr<IResource> delayed = it->second;
					if(delayed == resource)
						delayedResources.erase(it++);
					else
						it++;
				}
			}
		}
		else
		{
			// Find an existing add callback.
			AddResourcesCallbacks::iterator it = addResourcesCallbacks.find(ResourceBinding(message->entityId, message->resourceId));
			if(it == addResourcesCallbacks.end())
				return;

			// Veto the callback.
			boost::shared_ptr<EntityAddResourceCallback> callback = it->second;
			callback->veto = true;
		}
	}

	void ClientSession::OnResourceState(boost::shared_ptr<ResourceState> message)
	{
		// Find the resource.
		boost::shared_ptr<IResource> resource =
				Engine::Get().GetResourceManager()->FindResource(message->resourceId);

		if(!resource)
		{
			//LogConsole->Warning("Received unknown resource update.");
			return;
		}

		// Create the stream.
		boost::shared_ptr<IDataFilter> stream(new BufferFilter(message->statesize, message->state.c_array()));

		// Update the resource.
		resource->UpdateState(stream);
	}

	void ClientSession::OnFinishAdd(boost::shared_ptr<EntityAddResourceCallback> add)
	{
		// Remove the callback reference.
		addResourcesCallbacks.erase(ResourceBinding(add->entityId, add->resourceId));

		// Ignore the callback if was vetoed.
		if(add->veto)
			return;

		// Find the parent entity.
		boost::shared_ptr<Entity> parent = Engine::Get().GetWorldManager()->FindChildById(add->entityId);
		if(parent)
		{
			parent->AddResource(add->resource);
		}
		else
		{
			// Store the resource for delayed adding.
			delayedResources.insert(std::make_pair(add->entityId, add->resource));
		}
	}

	void ClientSession::OnFinishPhysicUpdate(boost::shared_ptr<EntityLoadedShapeCallback> update)
	{
		// Remove the callback reference.
		updatePhysicsCallback.erase(update->entity);

		// Ignore the callback if was vetoed.
		if(update->veto)
			return;

		// Find the parent entity.
		boost::shared_ptr<Entity> parent = Engine::Get().GetWorldManager()->FindChildById(update->entity);
		if(parent)
		{
			// Create the rigid body.
			boost::shared_ptr<IPhysicsEngine> physics = Engine::Get().GetPhysicsEngine();
			boost::shared_ptr<IRigidBody> rb;
			switch(update->type)
			{
			case IRigidBody::RBT_STATIC:
				rb = physics->CreateStaticBody(update->shape, parent);
				break;
			case IRigidBody::RBT_DYNAMIC:
				rb = physics->CreateDynamicBody(update->shape, parent);
				break;
			case IRigidBody::RBT_RIGIDBODY:
				rb = physics->CreateRigidBody(update->mass, update->shape, parent);
				break;
			default:
				return; // Unknown type.
			}

			// Set the movement factors.
			rb->SetLinearFactor(update->linearFactor);
			rb->SetAngularFactor(update->angularFactor);
			rb->SetSleepingThresholds(update->linearSleep, update->angularSleep);

			// Store the rigid body in the entity.
			parent->SetRigidBody(rb);
		}
		else
		{
			// Store the update for delayed entity creation.
			// Note: This case is unusual.
			updatePhysicsCallback.insert(std::make_pair(update->entity, update));
		}
	}

	void ClientSession::OnPlayerEntityResponse(boost::shared_ptr<PlayerEntityResponse> response)
	{
		// Make sure that we are waiting for the player.
		if(state != CS_WAIT_PLAYER)
			return;

		// Update the remote player id.
		remotePlayer->SetId(response->playerId);

		// Spawn the player.
		remotePlayer->Spawn();

		// Change the client state.
		state = CS_RUNNING;

		// Request a full update.
		boost::shared_ptr<RegionFullUpdateRequest> updateRequest(new RegionFullUpdateRequest());
		updateRequest->regionId = remoteRegion->GetId();
		session->Send(updateRequest);
	}

	void ClientSession::OnRegionListResponse(boost::shared_ptr<RegionListResponse> list)
	{
		// Make sure that we are waiting for the region.
		if(state != CS_WAIT_REGION)
			return;

		// TODO: Select a "factible" region.
		// Select the first entry.
		if(list->entries.size() == 0)
		{
			// TODO: do something when there aren't regions in the server.
			return;
		}

		// Get the base data.
		std::string basedata = list->entries[0].basedata;

		// Create the remote region.
		remoteRegion.reset(new RemoteRegion());
		uuid id = list->entries[0].id;
		if(!id.is_nil())
			remoteRegion->SetId(id);

		// Load the region data.
		try
		{
			RegionLoader loader;
			loader.LoadFromResource(basedata, "Maps", remoteRegion);
		}
		catch(XernException &e)
		{
			LogConsole->Error("Failed to load region %s.", e.what());
		}

		// TODO: if the region couldn't be loaded, download the data over the network.

		// Add into the world manager.
		boost::shared_ptr<WorldManager> worldMan = Engine::Get().GetWorldManager();
		worldMan->AddChild(remoteRegion);

		// Spawn the region.
		remoteRegion->Spawn();

		// Notify the client module.
		boost::shared_ptr<IClientModule> client = Engine::Get().GetClientModule();
		client->RegionLoaded(remoteRegion);
	}

	boost::shared_ptr<Entity> ClientSession::CreatePlayerController(boost::shared_ptr<Region> region)
	{
		if(!region || region != remoteRegion)
			throw XernException("Expected a remote region.");

		// Create the remote player.
		remotePlayer.reset(new RemotePlayer(session));

		// Don't spawn until the corresponding player in the server has been created.
		remoteRegion->AddChild(remotePlayer);

		// Change the client state.
		state = CS_WAIT_PLAYER;

		// Send a message to the server asking for the player controller.
		boost::shared_ptr<PlayerEntityRequest> request(new PlayerEntityRequest());
		request->regionId = region->GetId();
		session->Send(request);

		// Return the player.
		return remotePlayer;
	}
}; // namespace XernProtocol
